Skip to content

fix(mac): notice a deleted database, retire the bootstrap password, refuse concurrent restarts - #234

Merged
dvcdsys merged 1 commit into
developfrom
fix/firstrun-and-busy-state
Aug 10, 2026
Merged

fix(mac): notice a deleted database, retire the bootstrap password, refuse concurrent restarts#234
dvcdsys merged 1 commit into
developfrom
fix/firstrun-and-busy-state

Conversation

@dvcdsys

@dvcdsys dvcdsys commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Three defects, all found by walking the install path by hand rather than by
testing it.

A deleted database went unnoticed

needsFirstRun looked only for ~/.cix/server.env. Delete ~/.cix/data and
the app carried on as a configured install — the server recreates the directory
itself (db.go does MkdirAll), finds it empty, and mints a fresh admin from
the bootstrap credentials still sitting in server.env.

Observed live:

18:31:19 WARN bootstrap admin created from CIX_BOOTSTRAP_ADMIN_EMAIL + CIX_BOOTSTRAP_ADMIN_PASSWORD
18:31:19 WARN CIX_API_KEY imported as 'env-bootstrap' api_key

…with nothing on screen to say a database had gone, and with the original
generated password rather than whichever one had been set since.

Setup is now needed when either the config file or the database it names is
missing.

Not covered, and commented as such: a database file that exists but holds no
users. Answering that means opening SQLite from the launcher, which is a large
dependency for a case far rarer than "I deleted my data directory".

The bootstrap password never left

It seeds the first admin and has no purpose afterwards, but it stayed in
server.env and stayed authoritative — a credential outliving the account it
created, and silently overriding a password rotated in the dashboard the next
time the database was recreated.

It is now dropped once a running server proves the account exists. Bootstrap
runs before the HTTP listener opens, so an answering /health is that proof.
The email stays — it is not a credential, and the reset dialog offers it as the
address to reset.

Because the wizard can now run a second time, it merges onto the existing
server.env rather than rebuilding it from defaults. Reverting someone's port,
or worse their network-access choice, would be a bigger surprise than the one
this removes.

Two menu items could restart the server at once

Reported from use: click Start at Login, then Allow Network Access
while the restart is running, and the server hangs.

Both restart the server, each in its own goroutine, and both drive the same
launchd label — one boots the job out while the other is waiting for its pid to
disappear and then bootstraps it itself. Two bootstraps of one label leave
either a failure or two processes racing for the port.

A busy flag now gates every action that restarts the server, and render()
respects it — without that the poller would re-enable the controls five seconds
into the operation. The same guard covers a server that is still starting,
since a cold start takes minutes and a restart landing mid-boot is the same
wedge. Serialising rather than refusing would only queue a second restart
nobody asked for.

Reset Password stays available throughout: it opens the database directly,
so it neither needs a running server nor restarts one.

Tests

needsFirstRun across all four states, retireBootstrapPassword (removes only
the password, keeps the API key and the bind address, holds mode 0600, is
idempotent), and that re-running setup keeps choices already made.

🤖 Generated with Claude Code

…efuse

concurrent restarts

Three defects found by walking the install path by hand.

**A deleted database went unnoticed.** needsFirstRun looked only for
~/.cix/server.env. Delete ~/.cix/data and the app carried on as a configured
install: the server recreated the directory itself, found it empty, and minted
a fresh admin from the bootstrap credentials still sitting in server.env —
using the original generated password rather than whichever one the user had
set since. Observed live at 18:31, "bootstrap admin created from
CIX_BOOTSTRAP_ADMIN_EMAIL + CIX_BOOTSTRAP_ADMIN_PASSWORD" in the server log,
with nothing on screen to say a database had gone. Setup is now needed when
either the config file or the database it names is missing.

**The bootstrap password never left.** It seeds the first admin and has no use
afterwards, but it stayed on disk and stayed authoritative — a credential
outliving the account it created, and quietly overriding a password rotated in
the dashboard the next time the database was recreated. It is now removed once
a running server proves the account exists (bootstrap runs before the listener
opens, so an answering /health is that proof). The email stays: it is not a
credential, and the reset-password dialog offers it as a default.

Because the wizard can now run a second time, it merges onto the existing
server.env instead of rebuilding it from defaults. Reverting someone's port or,
worse, their network-access choice would be a bigger surprise than the one this
removes.

**Two menu items could restart the server at once.** Start at Login and Allow
Network Access each restart it, each in its own goroutine, and each drives the
same launchd label: one boots the job out while the other waits for its pid to
disappear and then bootstraps it itself. Two bootstraps of one label leave
either a failure or two processes racing for the port — from the outside, a
server that went away and did not come back. Reported from use: click one, then
the other during the restart, and the server hangs.

A busy flag now gates every action that restarts the server, and render()
respects it — without that the poller would re-enable the controls five seconds
into the operation. The same guard covers a server that is still starting: a
cold start loads an embedding model and takes minutes, and a restart landing in
the middle of that is exactly the wedge. Serialising instead of refusing would
only queue a second restart nobody asked for.

Reset Password stays available throughout: it opens the database directly and
neither needs a running server nor restarts one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dvcdsys
dvcdsys merged commit 0795df7 into develop Aug 10, 2026
2 checks passed
@dvcdsys
dvcdsys deleted the fix/firstrun-and-busy-state branch August 10, 2026 21:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant